<!doctype html>
<html lang="zh-CN">
<head>

    <meta charset="utf-8">
    <meta name="generator" content="Hugo 0.74.1" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>flutter Jenkins&#43;fastlane 自动化打测试包, 并上传蒲公英 | caijinglong的博客</title>
    <meta property="og:title" content="flutter Jenkins&#43;fastlane 自动化打测试包, 并上传蒲公英 - caijinglong的博客">
    <meta property="og:type" content="article">
        
    <meta property="article:published_time" content="2020-08-19T09:10:29&#43;08:00">
        
        
    <meta property="article:modified_time" content="2020-08-19T09:10:29&#43;08:00">
        
    <meta name="Keywords" content="golang,go语言,flutter,caijinglong,java,android,博客,项目管理,python,软件架构,公众号,小程序">
    <meta name="description" content="flutter Jenkins&#43;fastlane 自动化打测试包, 并上传蒲公英">
        
    <meta name="author" content="caijinglong">
    <meta property="og:url" content="http://www.kikt.top/posts/flutter/jenkins&#43;fastlane/">
    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">

    <link rel="stylesheet" href="/css/normalize.css">
    
        <link rel="stylesheet" href="/css/prism.css">
    
    <link rel="stylesheet" href="/css/style.css">
    <script type="text/javascript" src="//cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>

    

   
    <script>
        (function(){
            var bp = document.createElement('script');
            var curProtocol = window.location.protocol.split(':')[0];
            if (curProtocol === 'https') {
                bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
            }
            else {
                bp.src = 'http://push.zhanzhang.baidu.com/push.js';
            }
            var s = document.getElementsByTagName("script")[0];
            s.parentNode.insertBefore(bp, s);
        })();
    </script>

    
    
</head>

<body>
<header id="header" class="clearfix">
    <div class="container">
        <div class="col-group">
            <div class="site-name ">
                
                    <a id="logo" href="http://www.kikt.top">
                        caijinglong的博客
                    </a>
                
                <p class="description">Android/Flutter开发者，对于golang/python/ios/java 均有所了解</p>
            </div>
            <div>
                <nav id="nav-menu" class="clearfix">
                    <a class="" href="http://www.kikt.top">首页</a>
                    
                    <a  href="http://www.kikt.top/archives/" title="归档">归档</a>
                    
                    <a  href="http://www.kikt.top/contact/" title="接单">接单</a>
                    
                    <a  href="http://www.kikt.top/about/" title="关于">关于</a>
                    
                </nav>
            </div>
        </div>
    </div>
</header>


<div id="body">
    <div class="container">
        <div class="col-group">

            <div class="col-8" id="main">
                <div class="res-cons">
                    <article class="post">
                        <header>
                            <h1 class="post-title">flutter Jenkins&#43;fastlane 自动化打测试包, 并上传蒲公英</h1>
                        </header>
                        <date class="post-meta meta-date">
                            2020年8月19日
                        </date>
                        
                        <div class="post-meta meta-category">
                            |
                            
                                <a href="http://www.kikt.top/categories/flutter">flutter</a>
                            
                        </div>
                        
                        
                        <div class="post-meta">
                            <span id="busuanzi_container_page_pv">|<span id="busuanzi_value_page_pv"></span><span> 阅读</span></span>
                        </div>
                        
                        <div class="post-content">
                            <p>自动化打包上传是一个释放双手的过程, 本文就简单重复一下打包的过程</p>
<p>本文包括 iOS 和 android 篇.</p>
<p>如果有需要, macOS 和 web 也可以加进来, 步骤并不复杂, 但本篇暂时不讲</p>
<h2 id="开发环境">开发环境</h2>
<ul>
<li>macOS, 如果你不是 macOS, 那你只能打包安卓部分</li>
<li>Xcode, 因为 Xcode 会自动安装包含 git 在内的一些其他环境, 所以理论上有这个就够了</li>
<li>brew</li>
<li>android sdk</li>
<li>flutter sdk</li>
<li>jenkins</li>
<li>fastlane</li>
</ul>
<p>到 flutter sdk 为止就不讲了, 我相信看这篇文章的人, 起码 flutter 程序已经开发完了, 只是因为修改个文案就要打包的事被搞的不胜其扰, 才决定自动化的</p>
<p>而开发 flutter 基本上可以理解为包含如下工具/SDK</p>
<ul>
<li>ruby</li>
<li>git</li>
<li>Xcode</li>
<li>brew</li>
<li>Android SDK</li>
<li>Flutter SDK</li>
</ul>
<h3 id="jenkins">Jenkins</h3>
<p>有几点要写在前面, 这东西建议使用 brew 安装, 当然你要是坚持, 也可以用 jendins 的 war/jar 包自己部署,见仁见智</p>
<p>但<strong>不要</strong>使用 docker 的方式, 因为 docker 不包含 xcode 的环境, 执行起来比较麻烦</p>
<ol>
<li><code>brew install jenkins-lts</code> 安装命令, 执行这个等着就好了, 具体的过程就不说了, 用过 brew 的人都懂</li>
<li>启动, 这里使用一个小技巧启动<code>nohup jenkins-lts &gt; /tmp/jenkins.log 2&gt;&amp;1 &amp;</code> 用 nohup 的方式启动, 好处有如下两个
<ol>
<li>命令行窗口可以关闭, 不重启则不用重新运行, 当然你要是自己建 service 也可以, 看你自己的水平</li>
<li>可以记录一些日志, 当然我这里觉得不重要, 所以暂时先放在/tmp 文件夹里, 如果需要保留, 则放在别的文件夹里, 因为 /tmp 内的文件重启后会被自动删除</li>
</ol>
</li>
<li>启动,在浏览里打开<code>http://localhost:8080/</code></li>
</ol>
<p>这里要注意几点</p>
<ol>
<li>初始化运行的时候, 根据提示, 去查看对应的初始化密码, macOS 一般是在启动后在 <code>~/.jenkins/secrets/initialAdminPassword</code>位置, <code>~</code> 代表你的用户目录, 比如我的就是 <code>/Users/Caijinglong</code></li>
<li>初始化后, 建议安装的插件你就都安装上就行, 不用考虑那么多</li>
<li>用户名和密码一定要记住, 不然后面很麻烦</li>
</ol>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597804098.png" alt="image-20200819102817684"></p>
<p>当一切准备就绪的时候, 可能是这样的, 也可能是这样的</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597804135.png" alt="image-20200819102855120"></p>
<p>我这里是因为已经加入了一些任务, 初始化的应该是空的.</p>
<h4 id="安装插件">安装插件</h4>
<p>系统管理, 插件管理</p>
<p><!-- raw HTML omitted --></p>
<p><!-- raw HTML omitted --></p>
<p>这里建议安装的插件有如下几个</p>
<ol>
<li><a href="https://plugins.jenkins.io/upload-pgyer">Upload to pgyer</a> , 发布测试包到蒲公英的插件</li>
<li><a href="https://plugins.jenkins.io/jenkins-multijob-plugin">Multijob plugin</a> 这个插件是能把多个任务结合到一起的插件, 方便插入子任务, 比如我有 build ios 和 build android 两个任务, 这东西可以指定顺序, 让两个任务顺序执行</li>
</ol>
<p>Jenkins 的安装告一段落, 具体的配置到了配置篇再写</p>
<h3 id="fastlane">FastLane</h3>
<p><a href="https://fastlane.tools/">官网</a></p>
<p>这东西的安装看你的实际情况, 一般情况下, 使用 brew 安装也是可以的, 然后, 这东西是一个 ruby 写的自动化打包的工具, 也可以用 gem 安装</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ brew install fastlane
</code></pre></div><div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ sudo gem install fastlane
</code></pre></div><p>brew 发布的版本不是最新的, 所以, 根据你的情况选择使用哪种方式安装, 提示: sudo 会要求输入密码(系统的登录密码, 输入完回车)</p>
<h3 id="蒲公英账号">蒲公英账号</h3>
<p>官网: <a href="https://pgyer.com">https://pgyer.com</a></p>
<p>具体的申请步骤就不讲了, 然后, 现在发布 apk 需要实名制, 自己传身份吧</p>
<p><!-- raw HTML omitted --></p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597805891.png" alt="image-20200819105811061"></p>
<p>这里的信息上传的时候会用到, 先</p>
<h2 id="编写打包脚本">编写打包脚本</h2>
<p>在使用 Jenkins 之前需要先在外部环境尝试, 因为 Jenkins 中调试错误比较麻烦</p>
<p>创建个测试项目先</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ flutter create auto_build_example
$ code auto_build_example
</code></pre></div><p>我这里用 vscode 打开, 大家呢则根据自己的习惯</p>
<h3 id="安卓打包脚本">安卓打包脚本</h3>
<p>这个很简单, 其实只需要<code>flutter build apk --release -v --split-per-abi</code> 就可以了, 你则看自己的情况修改参数即可</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#f92672">[</span>   +3 ms<span style="color:#f92672">]</span> ✓ Built build/app/outputs/apk/release/app-armeabi-v7a-release.apk <span style="color:#f92672">(</span>5.1MB<span style="color:#f92672">)</span>.
<span style="color:#f92672">[</span>        <span style="color:#f92672">]</span> ✓ Built build/app/outputs/apk/release/app-arm64-v8a-release.apk <span style="color:#f92672">(</span>5.4MB<span style="color:#f92672">)</span>.
<span style="color:#f92672">[</span>        <span style="color:#f92672">]</span> ✓ Built build/app/outputs/apk/release/app-x86_64-release.apk <span style="color:#f92672">(</span>5.6MB<span style="color:#f92672">)</span>.
</code></pre></div><p>打包结尾会有具体放置的位置, 我个人建议使用 v7a 的, 也就是<code>build/app/outputs/apk/release/app-armeabi-v7a-release.apk</code>, 因为某些 2018 2019 年的手机甚至都还是 v7a 架构(实测), 所以嘛, 先这样</p>
<p>我们需要记录的信息就是 打包命令 和 打包产物的位置</p>
<h3 id="ios-打包">iOS 打包</h3>
<p>这个就是需要装 fastlane 的原因了, 因为使用命令行打包的命令比较复杂, 而且还有证书相关的问题, 很容易遇到问题.</p>
<p>但很多问题 fastlane 都帮我们解决过了</p>
<p><strong>注意</strong>: 本篇的打包是 ad-hoc 的, 而不是 appstore 的, 所以想要使用的人, udid 必须被包含在证书内, 然后, 这个是有名额限制的, 一个自然年内, 只可以有 100 个, 来年是否继续有效, 需要续约, 具体添加的方法可以自行搜索一下(提示, 直接通过 usb 线插在电脑上, 通过 xcode register device 的会被包含在这个名额内)</p>
<h4 id="初始化-fastlane-配置">初始化 fastlane 配置</h4>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ fastlane init
</code></pre></div><p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597814045.png" alt="image-20200819131404802"></p>
<p>这里有 4 个选项</p>
<ol>
<li>自动截图</li>
<li>自动发布到 beta 的 TestFlight</li>
<li>自动发布到 AppStore</li>
<li>手动配置</li>
</ol>
<p>因为我们其实目前的目的是打包 ad-hoc 的包, 给测试人员安装, 而不用给每个人都插上手机, 所以我们选 4</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597814570.png" alt="image-20200819132250411"></p>
<p>稍等片刻后会让连点几下回车, 主要就是把文档之类的地址什么的贴上来, 让有问题的朋友可以快速点进去</p>
<p>然后就是, 我这的版本不是最新版,提示应该更新之类的</p>
<h4 id="修改配置文件">修改配置文件</h4>
<p>ios 目录下会多一个 fastlane 目录</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ tree fastlane
fastlane
├── Appfile
└── Fastfile
</code></pre></div><p>Appfile 是 app 的信息</p>
<p>Fastfile 是配置信息</p>
<p>我们先修改 Fastfile</p>
<p>默认的是这样的</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby"><span style="color:#75715e"># This file contains the fastlane.tools configuration</span>
<span style="color:#75715e"># You can find the documentation at https://docs.fastlane.tools</span>
<span style="color:#75715e">#</span>
<span style="color:#75715e"># For a list of all available actions, check out</span>
<span style="color:#75715e">#</span>
<span style="color:#75715e">#     https://docs.fastlane.tools/actions</span>
<span style="color:#75715e">#</span>
<span style="color:#75715e"># For a list of all available plugins, check out</span>
<span style="color:#75715e">#</span>
<span style="color:#75715e">#     https://docs.fastlane.tools/plugins/available-plugins</span>
<span style="color:#75715e">#</span>

<span style="color:#75715e"># Uncomment the line if you want fastlane to automatically update itself</span>
<span style="color:#75715e"># update_fastlane</span>

default_platform(<span style="color:#e6db74">:ios</span>)

platform <span style="color:#e6db74">:ios</span> <span style="color:#66d9ef">do</span>
  desc <span style="color:#e6db74">&#34;Description of what the lane does&#34;</span>
  lane <span style="color:#e6db74">:custom_lane</span> <span style="color:#66d9ef">do</span>
    <span style="color:#75715e"># add actions here: https://docs.fastlane.tools/actions</span>
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span>
</code></pre></div><p>默认的长这样, 是 ruby 语法, 和 cocoapod 的配置文件语法基本相同, do end 可以简单理解为{}, 其中就是具体要执行的代码了</p>
<p><code>lane :custom_lane do</code> 这行的 custom_lane 就是任务的名字, 里面可以添加 actions, 会按照顺序执行</p>
<p>我们可以按照提示打开 <a href="https://docs.fastlane.tools/actions">https://docs.fastlane.tools/actions</a> 看看都有啥东西, 比如添加一个 <a href="https://docs.fastlane.tools/actions/build_ios_app/">build_ios_app</a></p>
<p>然后我把任务名字修改为 ad_hoc</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby">default_platform(<span style="color:#e6db74">:ios</span>)

platform <span style="color:#e6db74">:ios</span> <span style="color:#66d9ef">do</span>
  desc <span style="color:#e6db74">&#34;Description of what the lane does&#34;</span>
  lane <span style="color:#e6db74">:ad_hoc</span> <span style="color:#66d9ef">do</span>
    <span style="color:#75715e"># add actions here: https://docs.fastlane.tools/actions</span>
    build_ios_app
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span>
</code></pre></div><p>然后执行</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ fastlane ad_hoc
</code></pre></div><pre><code class="language-log" data-lang="log">2020-08-19 13:41:19.521 xcodebuild[64867:9469238] Unknown simulator cpu type (16777228)

❌  error: Signing for &quot;Runner&quot; requires a development team. Select a development team in the Signing &amp; Capabilities editor. (in target 'Runner' from project 'Runner')


** ARCHIVE FAILED **
[13:41:21]: Exit status: 65
[13:41:21]:
[13:41:21]: Maybe the error shown is caused by using the wrong version of Xcode
[13:41:21]: Found multiple versions of Xcode in '/Applications/'
[13:41:21]: Make sure you selected the right version for your project
[13:41:21]: This build process was executed using '/Applications/Xcode.app'
[13:41:21]: If you want to update your Xcode path, either
[13:41:21]:
[13:41:21]: - Specify the Xcode version in your Fastfile
[13:41:21]: ▸ xcversion(version: &quot;8.1&quot;) # Selects Xcode 8.1.0
[13:41:21]:
[13:41:21]: - Specify an absolute path to your Xcode installation in your Fastfile
[13:41:21]: ▸ xcode_select &quot;/Applications/Xcode8.app&quot;
[13:41:21]:
[13:41:21]: - Manually update the path using
[13:41:21]: ▸ sudo xcode-select -s /Applications/Xcode.app
[13:41:21]:

+-------------+-------------------------+
|           Build environment           |
+-------------+-------------------------+
| xcode_path  | /Applications/Xcode.app |
| gym_version | 2.156.1                 |
| sdk         | iPhoneOS13.5.sdk        |
+-------------+-------------------------+

[13:41:21]: ▸ note: Using new build system
[13:41:21]: ▸ note: Building targets in parallel
[13:41:21]: ▸ note: Planning build
[13:41:21]: ▸ note: Constructing build description
[13:41:21]: ▸ error: Signing for &quot;Runner&quot; requires a development team. Select a development team in the Signing &amp; Capabilities editor. (in target 'Runner' from project 'Runner')
[13:41:21]:
[13:41:21]: ⬆️  Check out the few lines of raw `xcodebuild` output above for potential hints on how to solve this error
[13:41:21]: 📋  For the complete and more detailed error log, check the full log at:
[13:41:21]: 📋  /Users/jinglongcai/Library/Logs/gym/Runner-Runner.log
[13:41:21]:
[13:41:21]: Looks like fastlane ran into a build/archive error with your project
[13:41:21]: It's hard to tell what's causing the error, so we wrote some guides on how
[13:41:21]: to troubleshoot build and signing issues: https://docs.fastlane.tools/codesigning/getting-started/
[13:41:21]: Before submitting an issue on GitHub, please follow the guide above and make
[13:41:21]: sure your project is set up correctly.
[13:41:21]: fastlane uses `xcodebuild` commands to generate your binary, you can see the
[13:41:21]: the full commands printed out in yellow in the above log.
[13:41:21]: Make sure to inspect the output above, as usually you'll find more error information there
[13:41:21]:
+------------------+------------+
|         Lane Context          |
+------------------+------------+
| DEFAULT_PLATFORM | ios        |
| PLATFORM_NAME    | ios        |
| LANE_NAME        | ios ad_hoc |
+------------------+------------+
[13:41:21]: Error building the application - see the log above

+------+------------------+-------------+
|           fastlane summary            |
+------+------------------+-------------+
| Step | Action           | Time (in s) |
+------+------------------+-------------+
| 1    | default_platform | 0           |
| 💥   | build_ios_app    | 5           |
+------+------------------+-------------+

[13:41:21]: fastlane finished with errors

[!] Error building the application - see the log above
</code></pre><p>好吧, 有一堆的日志, 其中会告诉我们哪个任务出错了, 原因是啥</p>
<p>我这里检查了一下, 这里是签名问题, 查一下 fastlane 里的解决方案, 告诉我们使用<a href="https://docs.fastlane.tools/actions/match/">match</a>来解决</p>
<h5 id="配置-match">配置 match</h5>
<p>match 其实是将一些签名的配置文件储存在某个位置, 然后达到多设备能同步的目的</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ fastlane match init
</code></pre></div><p><!-- raw HTML omitted --></p>
<p>fastlane 提供了三种储存方案, 你如果有谷歌云或者 S3, 也可以选择用那两个, 然后根据提示来</p>
<p>这里我选择 git, 毕竟<strong>贫穷限制了我</strong>, 我去 gitee 创建仓库, 你也可以存在任何你认为合适的 git 仓库</p>
<p><!-- raw HTML omitted --></p>
<p><!-- raw HTML omitted --></p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597816389.png" alt="image-20200819135309803"></p>
<p>这里输入你的苹果账号</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597816442.png" alt="image-20200819135402586"></p>
<p>我这里因为这个账号对应有 2 个公司开发者, 所以需要选一个. 输入 1 或 2 回车继续, 如果你只有一个, 估计这一步会自动跳过</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597816529.png" alt="image-20200819135529057"></p>
<p>下一步是输入你的 app 的 bundle id</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597816666.png" alt="image-20200819135746740"></p>
<p>这一步是提示, 没有找到签名文件, 其实这是因为这一步只会找现成的签名, 如果没有的话则不会自动创建, 我们去 xcode 里创建一个签名</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597816745.png" alt="image-20200819135904913"></p>
<p>在这里选择一个 Team, 然后等成功后再重新执行上面的 match adhoc</p>
<p>这里可能会有朋友发现, 还是会报错, 无法 match, 其实这是因为你的帐号在 appstore 上并没有对应的 App 的原因, 两个解决方案</p>
<ol>
<li>选择已有的 app bundle id</li>
<li>创建新的 app</li>
</ol>
<p>我这里为了演示, 在公司的帐号上创建一个 app(不发布就行了 😁)</p>
<p><a href="https://appstoreconnect.apple.com/">https://appstoreconnect.apple.com/</a></p>
<ol>
<li><a href="https://developer.apple.com/account/resources/identifiers/bundleId/add/bundle">https://developer.apple.com/account/resources/identifiers/bundleId/add/bundle</a> 创建新的&quot;套装 ID&rdquo;</li>
<li><a href="https://appstoreconnect.apple.com/apps">https://appstoreconnect.apple.com/apps</a> 新建一个 app</li>
</ol>
<p><!-- raw HTML omitted --></p>
<p><!-- raw HTML omitted --></p>
<p><!-- raw HTML omitted --></p>
<p>然后再去执行 match 就可以成功了</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597823335.png" alt="image-20200819154855400"></p>
<p>然后查看 git 仓库</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597823381.png" alt="image-20200819154941746"></p>
<p>发现也有东西了, 接着就可以回去配置 match 了</p>
<p>修改 Fastline</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby">platform <span style="color:#e6db74">:ios</span> <span style="color:#66d9ef">do</span>
  desc <span style="color:#e6db74">&#34;Description of what the lane does&#34;</span>
  lane <span style="color:#e6db74">:ad_hoc</span> <span style="color:#66d9ef">do</span>
    <span style="color:#75715e"># add actions here: https://docs.fastlane.tools/actions</span>
    match(
      <span style="color:#e6db74">type</span>: <span style="color:#e6db74">&#34;adhoc&#34;</span>,
      <span style="color:#e6db74">app_identifier</span>: <span style="color:#e6db74">&#34;top.kikt.autobuildexample&#34;</span>,
      <span style="color:#e6db74">team_id</span>: <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#75715e"># 你的team id, 只有一个可以直接删了这行</span>
    ) <span style="color:#75715e"># 添加这个</span>
    build_ios_app(
      <span style="color:#e6db74">scheme</span>: <span style="color:#e6db74">&#34;Release&#34;</span>,
      <span style="color:#e6db74">export_options</span>: {
        method: <span style="color:#e6db74">&#34;ad-hoc&#34;</span>,
      }
    )
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span>
</code></pre></div><p>team_id 注意: 如果你的 apple 帐号, 只对应一个公司, 则不用填这个, 填这个的目的是为了自动化</p>
<p>接着执行</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ fastlane ad_hoc
</code></pre></div><p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597825731.png" alt="image-20200819162851287"></p>
<p>多出来的两个文件, 一个是 ipa 包, 一个是对应的 dsym(用于还原堆栈)</p>
<p>这个是 ad-hoc, 所以可能不需要两步验证, 但拿出来说一下, 也许后面会用到</p>
<h4 id="两步验证的问题">两步验证的问题</h4>
<p>iOS 中打包可能会遇到两步验证的问题, 而且经常要验证, 在自己本地命令行打包的时候还可以交互一下, 但 Jenkins 是不支持交互的, 这个能否解决呢?</p>
<p>答案是肯定的, 但需要经过如下的步骤</p>
<ol>
<li>
<p><a href="https://appleid.apple.com/">创建 app 专用密码</a> <img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597824255.png" alt="image-20200819160414854"></p>
</li>
<li>
<p>获取 Cookie</p>
<ol>
<li>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ export FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD<span style="color:#f92672">=</span>&lt;PWD&gt; <span style="color:#75715e"># 上一步的app专用密码</span>
</code></pre></div></li>
<li>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ fastlane spaceauth -u &lt;apple_id&gt; <span style="color:#75715e"># apple id</span>
</code></pre></div></li>
</ol>
</li>
<li>
<p>将 cookie 保存下载, 至于是环境变量还是文件看你自己的情况, 我这里选择的是文件的方式, 然后存到代码仓库中以便于 jenkins 读取, 你存到环境变量里也是可以的</p>
</li>
</ol>
<p>比如如下的 cookie(为了安全性, 我这里删除了一部分字符, 所以你拿过去是用不了的) 请根据你自己的情况来拿到这个</p>
<pre><code class="language-log" data-lang="log">---\n- !ruby/object:HTTP::Cookie\n  name: DES5d68a2334f25aa752d511d\n  value: HSARMTKNSRVXWFla+6da0vxQCwQOICU+fwfLb6VaikjljuH6uDxOu6UWoLsJfDGnDolKcfUcKtnU5aQ5nskxgurrnxdOubXWidJF7JzxjbyzxmH6Tq/vD3JrCTSRVX\n  domain: idmsa.apple.com\n  for_domain: true\n  path: &quot;/&quot;\n  secure: true\n  httponly: true\n  expires:\n  max_age: 2592000\n  created_at: 2020-08-11 09:56:46.642442000 +08:00\n  accessed_at: 2020-08-18 13:45:59.234609000 +08:00\n- !ruby/object:HTTP::Cookie\n  name: myacinfo\n  value: DAWTKNV2da5c169c5551bd40dc5d6ffd58c42656bc39c57326c4686e7ba67cf58b38a017214dba13ef41247a84ae2909fa56863ffda004b4fcec4402e48d0a8c6a38a8b1593411a21da63fdf0b95ad37d33567598e684da6390f9b525c51cf6e1ea6e42a021dc1b798703c769d8380a23f8ac2b6890dfdc92a035acb48  domain: apple.com\n  for_domain: true\n  path: &quot;/&quot;\n  secure: true\n  httponly: true\n  expires:\n  max_age:\n  created_at: 2020-08-18 13:46:00.175666000 +08:00\n  accessed_at: 2020-08-18 13:46:00.176071000 +08:00\n- !ruby/object:HTTP::Cookie\n  name: dqsid\n  value: mp0aSI6IlFYS3E3MmI1SmVkVFV5THFcsh58p8zWIpcqviQIYYqJ8oW18qxcM\n  domain: appstoreconnect.apple.com\n  for_domain: false\n  path: &quot;/&quot;\n  secure: true\n  httponly: true\n  expires:\n  max_age: 1800\n  created_at: &amp;1 2020-08-18 13:46:00.842456000 +08:00\n  accessed_at: *1\n
</code></pre><p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597825544.png" alt="image-20200819162543869"></p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597825529.png" alt="image-20200819162529836"></p>
<p>注意, 这里有两端&quot;乱码&rdquo;, 复制第一段, 第二段是 fastlane 给的示例而已, 这里提示输入 y,则自动复制到剪切板</p>
<p>我创建一个文件 cookies.txt, 把这段东西粘贴进去, 这一步是为了 Jenkins 考虑</p>
<h3 id="小结">小结</h3>
<p>总体来说, 配置好了以后</p>
<p>安卓:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ flutter build apk
</code></pre></div><p>iOS:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ flutter build ios --release -v
$ cd ios
$ fastlane ad_hoc
</code></pre></div><p>打包的命令熟悉完毕后, 就到了 Jenkins 的配置了</p>
<h2 id="jenkins-的配置">Jenkins 的配置</h2>
<h3 id="配置仓库权限">配置仓库权限</h3>
<p>Jenkins 中支持 git 来管理源码, 然后, git 需要凭证来访问, 我这里是 gitee 的, 你可以根据你的情况自己配置</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597827090.png" alt="image-20200819165130173"></p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597828483.png" alt="image-20200819171443197"></p>
<h3 id="安卓自动化">安卓自动化</h3>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597826489.png" alt="image-20200819164129138"></p>
<p>我先新建一个组叫&quot;自动构建&rdquo;, 然后点击创建新的任务</p>
<p><!-- raw HTML omitted --></p>
<p>选择构建一个自由风格的软件项目</p>
<h4 id="配置源码">配置源码</h4>
<p>先配置项目的 git 地址</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597828549.png" alt="image-20200819171549309"></p>
<p>然后选择凭证, 接着确定后, 点下面的保存, 先试试是否能拉到源码</p>
<p><!-- raw HTML omitted --></p>
<p>这里构建完成后, 可以看到构建过程</p>
<p>然后点工作空间看看代码</p>
<p><!-- raw HTML omitted --></p>
<p>现在, 就能看到完整的代码了</p>
<h4 id="配置打包脚本">配置打包脚本</h4>
<p>这里还没完, 需要配置打包脚本, 继续点击配置</p>
<p>拉到后面, 点击构建=&gt;执行 shell</p>
<p>这里就是自动构建的步骤了.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">flutter build apk --release -v --split-per-abi
ls build/app/outputs/apk/release/
</code></pre></div><p>这里简单打包, 然后查看下, 保存后点击立即构建</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597829371.png" alt="image-20200819172922516"></p>
<p>这里就是打包过程了</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597829400.png" alt="image-20200819173000100"></p>
<p>查看一下控制台输出</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597829421.png" alt="image-20200819173021433"></p>
<p>打包完成了, 顺便执行了 ls 命令查看了一下</p>
<h4 id="上传">上传</h4>
<p>上传直接使用插件就可以了,还记得之前安装过 pgyer 的插件吗, 如果你要传 fir 或者自己的服务器, 那就需要你自己探索一下了</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597829695.png" alt="image-20200819173455311"></p>
<p>使用 v1 还是 v2 看你自己的情况, 都可以, 我这里使用 v2 的, 把 api_key 放过来, 然后使用</p>
<p>scandir 是你上传的目录 <code>${WORKSPACE}/build/app/outputs/apk/release</code> 工作空间的根目录, 然后下面是文件名</p>
<p>然后保存, 立即构建</p>
<p><!-- raw HTML omitted --></p>
<p>地址: <a href="https://www.pgyer.com/DJrV">https://www.pgyer.com/DJrV</a></p>
<p><!-- raw HTML omitted --></p>
<p>到这一步, 安卓就算是成功了</p>
<h3 id="ios-自动化">iOS 自动化</h3>
<p>创建一个叫&quot;iOS 自动化&quot;的任务</p>
<h4 id="配置源码仓库">配置源码仓库</h4>
<p>和安卓完全一样, 略过</p>
<h4 id="打包脚本">打包脚本</h4>
<p>还是在构建位置,使用 shell 脚本, 但和安卓不同的时, 需要有如下的步骤</p>
<ol>
<li>flutter build ios</li>
<li>两步验证</li>
<li>fastlane</li>
</ol>
<p>1, 3 在外部测试阶段完成了, 也就是说, 唯一需要注意的是两步验证的问题, 仓库里储存有 cookies, 这个东西就是用来干这个的了</p>
<p>编写脚本</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">flutter build ios --release -v
cd ios
export FASTLANE_SESSION<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>cat login_cookie<span style="color:#66d9ef">)</span>
fastlane ad_hoc
ls Runner.*
</code></pre></div><h4 id="上传-1">上传</h4>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597885351.png" alt="image-20200820090230922"></p>
<p>然后, 我们遇到了一次失败</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597887223.png" alt="image-20200820093343644"></p>
<p>去查一下原因吧, 进入控制台输出</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597887306.png" alt="image-20200820093506157"></p>
<p>我们看到, 蒲公英上传失败了.</p>
<p>嗯, 那我们换一个方式, 不通过 jenkins 的插件了, 而是通过 fastlane 的 pgyer action 直接在 fastlane 里完成这一步, <a href="https://github.com/shishirui/fastlane-plugin-pgyer"><code>pgyer</code></a></p>
<p>先添加</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ fastlane add_plugin pgyer
</code></pre></div><p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597887611.png" alt="image-20200820094011798"></p>
<p>中间需要 y,确定一下</p>
<p>然后, 修改 fastfile</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby">default_platform(<span style="color:#e6db74">:ios</span>)

platform <span style="color:#e6db74">:ios</span> <span style="color:#66d9ef">do</span>
  desc <span style="color:#e6db74">&#34;Description of what the lane does&#34;</span>
  lane <span style="color:#e6db74">:ad_hoc</span> <span style="color:#66d9ef">do</span>
    <span style="color:#75715e"># add actions here: https://docs.fastlane.tools/actions</span>
  	<span style="color:#f92672">...</span>
    build_ios_app(
      <span style="color:#e6db74">scheme</span>: <span style="color:#e6db74">&#34;Release&#34;</span>,
      <span style="color:#e6db74">export_options</span>: {
        method: <span style="color:#e6db74">&#34;ad-hoc&#34;</span>,
      }
    )
    pgyer(<span style="color:#e6db74">api_key</span>: <span style="color:#e6db74">&#34;75123fxxx10&#34;</span>, <span style="color:#e6db74">user_key</span>: <span style="color:#e6db74">&#34;4853910323xxx22105382&#34;</span>)
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span>
</code></pre></div><p>把 pgyer 的 action 直接加载 build_ios_app 后, 然后设置到 api_key 和 user_key, 在 <a href="https://www.pgyer.com/account/api">https://www.pgyer.com/account/api</a> 查询</p>
<p>接着点击立即构建, 就可以触发了</p>
<p>上传完毕后是这样</p>
<p><!-- raw HTML omitted --></p>
<h2 id="jenkins-的触发器">Jenkins 的触发器</h2>
<p>Jenkins 支持一些触发器</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597885564.png" alt="image-20200820090604447"></p>
<ol>
<li>
<p>远程触发, 设置 token, 然后通过访问 url 的方式, 或是脚本之类的方式触发</p>
</li>
<li>
<p>其他工程构建后触发, 顾名思义, 就是比如我构建完安卓, 触发构建 iOS 的操作, 这样是可以细化构建步骤, 但如果本身没有关联, 强行搞到一起反而不美</p>
</li>
<li>
<p>定时构建, 这个支持比较多的方式, 很类似于 Spring 的定时任务, 具体语法可以查看说明或示例</p>
<p><img src="https://cdn.jsdelivr.net/gh/kikt-blog/blog-2@t1/uPic/1597885697.png" alt="image-20200820090817688"></p>
</li>
<li>
<p>github 的钩子</p>
</li>
<li>
<p>轮询 SCM</p>
</li>
</ol>
<h2 id="fastlane-的一些配置">Fastlane 的一些配置</h2>
<p>fastlane 其实也支持安卓, 不过本篇因为安卓 flutter build apk 已经足够简单, 所以个人考虑不需要使用这个, 如果你的项目是 flutter module 配合 maven 或 aar 的方式置入原生的, 需要更多步骤的情况下, 可以尝试使用 fastlane 来帮你解放双手</p>
<h2 id="后记">后记</h2>
<p>本篇主要探索了测试阶段如何解放双手, 使用 jenkins 配合 fastlane 达到自动打包, 上传的过程</p>
<p>如果需要额外告知某些人员, 可以尝试对接办公系统的 api 来达到自动通知的目的, 钉钉等程序应该都有对应接口</p>
<h3 id="本篇设计到的文件相对完整版">本篇设计到的文件相对完整版</h3>
<p>之所以是相对, 是因为有一些敏感信息, 例如 pgyer 的 api key, 苹果的登录 cookie, 签名仓库等等, 并不方便向所有人开放</p>
<ol>
<li>
<p>安卓篇</p>
<ol>
<li>
<p>Jenkins 构建脚本</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">flutter build apk --release
</code></pre></div></li>
<li>
<p>构建后</p>
<p>scandir: <code>${WORKSPACE}/build/app/outputs/apk/release</code></p>
<p>File name: <code>app-armeabi-v7a-release.apk</code></p>
</li>
</ol>
</li>
<li>
<p>iOS 篇</p>
<ol>
<li>
<p>Fastfile</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby"><span style="color:#75715e"># This file contains the fastlane.tools configuration</span>
<span style="color:#75715e"># You can find the documentation at https://docs.fastlane.tools</span>
<span style="color:#75715e">#</span>
<span style="color:#75715e"># For a list of all available actions, check out</span>
<span style="color:#75715e">#</span>
<span style="color:#75715e">#     https://docs.fastlane.tools/actions</span>
<span style="color:#75715e">#</span>
<span style="color:#75715e"># For a list of all available plugins, check out</span>
<span style="color:#75715e">#</span>
<span style="color:#75715e">#     https://docs.fastlane.tools/plugins/available-plugins</span>
<span style="color:#75715e">#</span>

<span style="color:#75715e"># Uncomment the line if you want fastlane to automatically update itself</span>
<span style="color:#75715e"># update_fastlane</span>

default_platform(<span style="color:#e6db74">:ios</span>)

platform <span style="color:#e6db74">:ios</span> <span style="color:#66d9ef">do</span>
  desc <span style="color:#e6db74">&#34;Description of what the lane does&#34;</span>
  lane <span style="color:#e6db74">:ad_hoc</span> <span style="color:#66d9ef">do</span>
    <span style="color:#75715e"># add actions here: https://docs.fastlane.tools/actions</span>
    match(
      <span style="color:#e6db74">type</span>: <span style="color:#e6db74">&#34;adhoc&#34;</span>,
      <span style="color:#e6db74">app_identifier</span>: <span style="color:#e6db74">&#34;top.kikt.autobuildexample&#34;</span>,
      <span style="color:#e6db74">team_id</span>: <span style="color:#e6db74">&#34;xxx&#34;</span>,
    )
    build_ios_app(
      <span style="color:#e6db74">scheme</span>: <span style="color:#e6db74">&#34;Release&#34;</span>,
      <span style="color:#e6db74">export_options</span>: {
        method: <span style="color:#e6db74">&#34;ad-hoc&#34;</span>,
      }
    )
    pgyer(<span style="color:#e6db74">api_key</span>: <span style="color:#e6db74">&#34;xxx&#34;</span>, <span style="color:#e6db74">user_key</span>: <span style="color:#e6db74">&#34;xxx&#34;</span>)
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span>
</code></pre></div></li>
<li>
<p>Matchfile</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby">git_url(<span style="color:#e6db74">&#34;https://gitee.com/kikt/test_adhoc_sign_store.git&#34;</span>)

storage_mode(<span style="color:#e6db74">&#34;git&#34;</span>)

type(<span style="color:#e6db74">&#34;adhoc&#34;</span>) <span style="color:#75715e"># The default type, can be: appstore, adhoc, enterprise or development</span>

app_identifier(<span style="color:#f92672">[</span><span style="color:#e6db74">&#34;top.kikt.autobuildexample&#34;</span><span style="color:#f92672">]</span>)
username(<span style="color:#e6db74">&#34;xxx&#34;</span>) <span style="color:#75715e"># Your Apple Developer Portal username</span>

<span style="color:#75715e"># For all available options run `fastlane match --help`</span>
<span style="color:#75715e"># Remove the # in the beginning of the line to enable the other options</span>

<span style="color:#75715e"># The docs are available on https://docs.fastlane.tools/actions/match</span>

</code></pre></div></li>
<li>
<p>Pluginfile</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby"><span style="color:#75715e"># Autogenerated by fastlane</span>
<span style="color:#75715e">#</span>
<span style="color:#75715e"># Ensure this file is checked in to source control!</span>

gem <span style="color:#e6db74">&#39;fastlane-plugin-pgyer&#39;</span>

</code></pre></div></li>
<li>
<p>jenkins</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby">flutter build ios <span style="color:#f92672">--</span>release <span style="color:#f92672">-</span>v
cd ios
export <span style="color:#66d9ef">FASTLANE_SESSION</span><span style="color:#f92672">=</span><span style="color:#960050;background-color:#1e0010">$</span>(cat login_cookie)
fastlane ad_hoc
ls <span style="color:#66d9ef">Runner</span><span style="color:#f92672">.</span>*
</code></pre></div></li>
</ol>
</li>
</ol>
<p>以上</p>

                        </div>

                        

<div class="post-archive">
    <h2>See Also</h2>
    <ul class="listing">
        
        <li><a href="/posts/flutter/dart/generators/">dart中的生成器函数</a></li>
        
        <li><a href="/posts/flutter/focusnode-2/">Flutter FocusNode 焦点那点事-(二)</a></li>
        
        <li><a href="/posts/flutter/focusnode-1/">Flutter FocusNode 焦点那点事-(一)</a></li>
        
        <li><a href="/posts/flutter/route/navigator-helper2/">Navigator Helper2</a></li>
        
        <li><a href="/posts/flutter/grey-app/">flutter 怎么实现app整体灰度</a></li>
        
    </ul>
</div>


                        <div class="post-meta meta-tags">
                            
                            <ul class="clearfix">
                                
                                <li><a href="http://www.kikt.top/tags/jenkins">jenkins</a></li>
                                
                                <li><a href="http://www.kikt.top/tags/fastlane">fastlane</a></li>
                                
                                <li><a href="http://www.kikt.top/tags/flutter">flutter</a></li>
                                
                            </ul>
                            
                        </div>
                    </article>
                    
    

    
    
    <div class="post bg-white">
      <script src="https://utteranc.es/client.js"
            repo= "caijinglong/kikt-blog-comment"
            issue-term="pathname"
            theme="github-light"
            crossorigin="anonymous"
            async>
      </script>
    </div>
    
                </div>
            </div>
            <div id="secondary">
    <section class="widget">
        <form id="search" action="//www.google.com/search" method="get" accept-charset="utf-8" target="_blank" _lpchecked="1">
      
      <input type="text" name="q" maxlength="20" placeholder="Search">
      <input type="hidden" name="sitesearch" value="http://www.kikt.top">
      <button type="submit" class="submit icon-search"></button>
</form>
    </section>
    
    <section class="widget">
        <h3 class="widget-title">最近文章</h3>
<ul class="widget-list">
    
    <li>
        <a href="http://www.kikt.top/posts/java/crack/crack1/" title="使用javassist,修改jar包方法实现">使用javassist,修改jar包方法实现</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/github/actions/create/" title="Github action 的开发到发布">Github action 的开发到发布</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/flutter/jenkins&#43;fastlane/" title="flutter Jenkins&#43;fastlane 自动化打测试包, 并上传蒲公英">flutter Jenkins&#43;fastlane 自动化打测试包, 并上传蒲公英</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/go/compile-for-android/" title="编译 go 源码为 android 动态库(so)">编译 go 源码为 android 动态库(so)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/flutter/focusnode-2/" title="Flutter FocusNode 焦点那点事-(二)">Flutter FocusNode 焦点那点事-(二)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/flutter/focusnode-1/" title="Flutter FocusNode 焦点那点事-(一)">Flutter FocusNode 焦点那点事-(一)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/server/caddy/cros_for_local_dev/" title="用 Caddy 解决 web 开发中本地跨域的问题">用 Caddy 解决 web 开发中本地跨域的问题</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/flutter/route/navigator-helper2/" title="Navigator Helper2">Navigator Helper2</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/flutter/grey-app/" title="flutter 怎么实现app整体灰度">flutter 怎么实现app整体灰度</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/flutter/plugin/flutter-sdk-import-aar/" title="Flutter 插件开发之引入aar到安卓部分 并使用本地maven">Flutter 插件开发之引入aar到安卓部分 并使用本地maven</a>
    </li>
    
</ul>
    </section>

    

    <section class="widget">
        <h3 class="widget-title">分类</h3>
<ul class="widget-list">
    
    <li>
        <a href="http://www.kikt.top/categories/android/">android(25)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/caddy/">caddy(1)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/dart/">dart(5)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/docker/">docker(3)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/flutter/">flutter(60)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/github/">github(3)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/go/">go(1)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/golang/">golang(1)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/iOS/">iOS(6)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/ios/">ios(1)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/java/">java(6)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/jetbrains/">jetbrains(1)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/other/">other(3)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/python/">python(1)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/server/">server(5)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/spring/">spring(3)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/tools/">tools(1)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/%E6%9D%82%E9%A1%B9/">杂项(1)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/%E6%B6%82%E9%B8%A6/">涂鸦(1)</a>
    </li>
    
</ul>
    </section>

    <section class="widget">
        <h3 class="widget-title">标签</h3>
<div class="tagcloud">
    
    <a href="http://www.kikt.top/tags/10/">10</a>
    
    <a href="http://www.kikt.top/tags/2.3/">2.3</a>
    
    <a href="http://www.kikt.top/tags/Clipboard/">Clipboard</a>
    
    <a href="http://www.kikt.top/tags/Javassist/">Javassist</a>
    
    <a href="http://www.kikt.top/tags/Migrate/">Migrate</a>
    
    <a href="http://www.kikt.top/tags/aar/">aar</a>
    
    <a href="http://www.kikt.top/tags/actions/">actions</a>
    
    <a href="http://www.kikt.top/tags/aidl/">aidl</a>
    
    <a href="http://www.kikt.top/tags/android/">android</a>
    
    <a href="http://www.kikt.top/tags/androidQ/">androidQ</a>
    
    <a href="http://www.kikt.top/tags/androidX/">androidX</a>
    
    <a href="http://www.kikt.top/tags/apk/">apk</a>
    
    <a href="http://www.kikt.top/tags/app/">app</a>
    
    <a href="http://www.kikt.top/tags/appcode/">appcode</a>
    
    <a href="http://www.kikt.top/tags/azure/">azure</a>
    
    <a href="http://www.kikt.top/tags/ble/">ble</a>
    
    <a href="http://www.kikt.top/tags/bottomsheet/">bottomsheet</a>
    
    <a href="http://www.kikt.top/tags/butterknife/">butterknife</a>
    
    <a href="http://www.kikt.top/tags/c/">c</a>
    
    <a href="http://www.kikt.top/tags/caddy/">caddy</a>
    
    <a href="http://www.kikt.top/tags/camera/">camera</a>
    
    <a href="http://www.kikt.top/tags/cameraX/">cameraX</a>
    
    <a href="http://www.kikt.top/tags/channel/">channel</a>
    
    <a href="http://www.kikt.top/tags/cocoapods/">cocoapods</a>
    
    <a href="http://www.kikt.top/tags/cupertino/">cupertino</a>
    
    <a href="http://www.kikt.top/tags/dart/">dart</a>
    
    <a href="http://www.kikt.top/tags/dart-2.6/">dart-2.6</a>
    
    <a href="http://www.kikt.top/tags/desktop/">desktop</a>
    
    <a href="http://www.kikt.top/tags/dialog/">dialog</a>
    
    <a href="http://www.kikt.top/tags/dmg/">dmg</a>
    
    <a href="http://www.kikt.top/tags/doc/">doc</a>
    
    <a href="http://www.kikt.top/tags/docker/">docker</a>
    
    <a href="http://www.kikt.top/tags/docker-compose/">docker-compose</a>
    
    <a href="http://www.kikt.top/tags/excel/">excel</a>
    
    <a href="http://www.kikt.top/tags/exists/">exists</a>
    
    <a href="http://www.kikt.top/tags/faq/">faq</a>
    
    <a href="http://www.kikt.top/tags/fastlane/">fastlane</a>
    
    <a href="http://www.kikt.top/tags/ffi/">ffi</a>
    
    <a href="http://www.kikt.top/tags/ffmpeg/">ffmpeg</a>
    
    <a href="http://www.kikt.top/tags/flexmark/">flexmark</a>
    
    <a href="http://www.kikt.top/tags/flutter/">flutter</a>
    
    <a href="http://www.kikt.top/tags/flutter-web/">flutter-web</a>
    
    <a href="http://www.kikt.top/tags/focus/">focus</a>
    
    <a href="http://www.kikt.top/tags/focusNode/">focusNode</a>
    
    <a href="http://www.kikt.top/tags/form/">form</a>
    
    <a href="http://www.kikt.top/tags/framework/">framework</a>
    
    <a href="http://www.kikt.top/tags/github/">github</a>
    
    <a href="http://www.kikt.top/tags/go/">go</a>
    
    <a href="http://www.kikt.top/tags/golang/">golang</a>
    
    <a href="http://www.kikt.top/tags/gradle/">gradle</a>
    
    <a href="http://www.kikt.top/tags/http/">http</a>
    
    <a href="http://www.kikt.top/tags/iOS/">iOS</a>
    
    <a href="http://www.kikt.top/tags/ide/">ide</a>
    
    <a href="http://www.kikt.top/tags/idea/">idea</a>
    
    <a href="http://www.kikt.top/tags/index/">index</a>
    
    <a href="http://www.kikt.top/tags/inside/">inside</a>
    
    <a href="http://www.kikt.top/tags/ios/">ios</a>
    
    <a href="http://www.kikt.top/tags/java/">java</a>
    
    <a href="http://www.kikt.top/tags/jenkins/">jenkins</a>
    
    <a href="http://www.kikt.top/tags/jetbrains/">jetbrains</a>
    
    <a href="http://www.kikt.top/tags/jsdelivr/">jsdelivr</a>
    
    <a href="http://www.kikt.top/tags/json/">json</a>
    
    <a href="http://www.kikt.top/tags/json_serializable/">json_serializable</a>
    
    <a href="http://www.kikt.top/tags/junit/">junit</a>
    
    <a href="http://www.kikt.top/tags/library/">library</a>
    
    <a href="http://www.kikt.top/tags/live-template/">live-template</a>
    
    <a href="http://www.kikt.top/tags/loadmore/">loadmore</a>
    
    <a href="http://www.kikt.top/tags/log/">log</a>
    
    <a href="http://www.kikt.top/tags/mac/">mac</a>
    
    <a href="http://www.kikt.top/tags/macos/">macos</a>
    
    <a href="http://www.kikt.top/tags/maven/">maven</a>
    
    <a href="http://www.kikt.top/tags/mysql/">mysql</a>
    
    <a href="http://www.kikt.top/tags/navigationbar/">navigationbar</a>
    
    <a href="http://www.kikt.top/tags/navigator/">navigator</a>
    
    <a href="http://www.kikt.top/tags/ndk/">ndk</a>
    
    <a href="http://www.kikt.top/tags/nginx/">nginx</a>
    
    <a href="http://www.kikt.top/tags/oc/">oc</a>
    
    <a href="http://www.kikt.top/tags/opencv/">opencv</a>
    
    <a href="http://www.kikt.top/tags/poi/">poi</a>
    
    <a href="http://www.kikt.top/tags/pub/">pub</a>
    
    <a href="http://www.kikt.top/tags/python/">python</a>
    
    <a href="http://www.kikt.top/tags/retrofit/">retrofit</a>
    
    <a href="http://www.kikt.top/tags/route/">route</a>
    
    <a href="http://www.kikt.top/tags/server/">server</a>
    
    <a href="http://www.kikt.top/tags/so/">so</a>
    
    <a href="http://www.kikt.top/tags/spp/">spp</a>
    
    <a href="http://www.kikt.top/tags/sprintboot/">sprintboot</a>
    
    <a href="http://www.kikt.top/tags/swift/">swift</a>
    
    <a href="http://www.kikt.top/tags/tap/">tap</a>
    
    <a href="http://www.kikt.top/tags/thymeleaf/">thymeleaf</a>
    
    <a href="http://www.kikt.top/tags/tips/">tips</a>
    
    <a href="http://www.kikt.top/tags/toast/">toast</a>
    
    <a href="http://www.kikt.top/tags/transition/">transition</a>
    
    <a href="http://www.kikt.top/tags/ui/">ui</a>
    
    <a href="http://www.kikt.top/tags/utf8/">utf8</a>
    
    <a href="http://www.kikt.top/tags/yield/">yield</a>
    
    <a href="http://www.kikt.top/tags/%E4%BA%8C%E8%BF%9B%E5%88%B6/">二进制</a>
    
    <a href="http://www.kikt.top/tags/%E4%BA%A4%E5%8F%89/">交叉</a>
    
    <a href="http://www.kikt.top/tags/%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91/">交叉编译</a>
    
    <a href="http://www.kikt.top/tags/%E4%BB%8B%E7%BB%8D/">介绍</a>
    
    <a href="http://www.kikt.top/tags/%E4%BB%A3%E7%A0%81%E8%A7%84%E8%8C%83/">代码规范</a>
    
    <a href="http://www.kikt.top/tags/%E5%86%B2%E7%AA%81/">冲突</a>
    
    <a href="http://www.kikt.top/tags/%E5%8A%A8%E6%80%81/">动态</a>
    
    <a href="http://www.kikt.top/tags/%E5%8F%91%E5%B8%83/">发布</a>
    
    <a href="http://www.kikt.top/tags/%E5%8F%AF%E6%89%A7%E8%A1%8C/">可执行</a>
    
    <a href="http://www.kikt.top/tags/%E5%9B%BE%E5%BA%8A/">图床</a>
    
    <a href="http://www.kikt.top/tags/%E5%9F%8E%E5%B8%82/">城市</a>
    
    <a href="http://www.kikt.top/tags/%E5%A4%A7%E6%96%87%E4%BB%B6/">大文件</a>
    
    <a href="http://www.kikt.top/tags/%E5%B7%A5%E5%85%B7/">工具</a>
    
    <a href="http://www.kikt.top/tags/%E5%B7%B2%E6%9C%89%E9%A1%B9%E7%9B%AE/">已有项目</a>
    
    <a href="http://www.kikt.top/tags/%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83/">开发环境</a>
    
    <a href="http://www.kikt.top/tags/%E6%80%BB%E7%BB%93/">总结</a>
    
    <a href="http://www.kikt.top/tags/%E6%85%A2/">慢</a>
    
    <a href="http://www.kikt.top/tags/%E6%89%93%E5%8C%85/">打包</a>
    
    <a href="http://www.kikt.top/tags/%E6%89%AB%E7%A0%81/">扫码</a>
    
    <a href="http://www.kikt.top/tags/%E6%8F%92%E4%BB%B6/">插件</a>
    
    <a href="http://www.kikt.top/tags/%E6%96%87%E6%A1%A3/">文档</a>
    
    <a href="http://www.kikt.top/tags/%E6%A8%A1%E6%9D%BF/">模板</a>
    
    <a href="http://www.kikt.top/tags/%E6%B8%B2%E6%9F%93/">渲染</a>
    
    <a href="http://www.kikt.top/tags/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/">源码解析</a>
    
    <a href="http://www.kikt.top/tags/%E7%81%B0%E5%BA%A6/">灰度</a>
    
    <a href="http://www.kikt.top/tags/%E7%82%B9%E5%87%BB/">点击</a>
    
    <a href="http://www.kikt.top/tags/%E7%84%A6%E7%82%B9/">焦点</a>
    
    <a href="http://www.kikt.top/tags/%E7%88%AC%E8%99%AB/">爬虫</a>
    
    <a href="http://www.kikt.top/tags/%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86/">状态管理</a>
    
    <a href="http://www.kikt.top/tags/%E7%99%BE%E5%BA%A6%E5%9C%B0%E5%9B%BE/">百度地图</a>
    
    <a href="http://www.kikt.top/tags/%E7%BC%96%E7%A0%81/">编码</a>
    
    <a href="http://www.kikt.top/tags/%E7%BC%96%E7%A8%8B%E6%8A%80%E5%B7%A7/">编程技巧</a>
    
    <a href="http://www.kikt.top/tags/%E7%BC%96%E8%AF%91/">编译</a>
    
    <a href="http://www.kikt.top/tags/%E7%BF%BB%E8%AF%91/">翻译</a>
    
    <a href="http://www.kikt.top/tags/%E8%93%9D%E7%89%99/">蓝牙</a>
    
    <a href="http://www.kikt.top/tags/%E8%A1%A8%E5%8D%95/">表单</a>
    
    <a href="http://www.kikt.top/tags/%E8%B7%AF%E7%94%B1/">路由</a>
    
    <a href="http://www.kikt.top/tags/%E8%BF%87%E5%9C%BA%E5%8A%A8%E7%94%BB/">过场动画</a>
    
    <a href="http://www.kikt.top/tags/%E9%9A%90%E7%A7%81/">隐私</a>
    
</div>
    </section>

    
<section class="widget">
    <h3 class="widget-title">友情链接</h3>
    <ul class="widget-list">
        
        <li>
            <a target="_blank" href="https://github.com/Caijinglong" title="我的Github">我的Github</a>
        </li>
        
        <li>
            <a target="_blank" href="https://flutter.dev" title="flutter 官网">flutter 官网</a>
        </li>
        
    </ul>
</section>


    <section class="widget">
        <h3 class="widget-title">其它</h3>
        <ul class="widget-list">
            <li><a href="http://www.kikt.top/index.xml">文章 RSS</a></li>
        </ul>
    </section>
</div>
        </div>
    </div>
</div>
<footer id="footer">
    <div class="container">
        <div>
            <p>如果我的blog对您有帮助,而您愿意捐赠 😆, 可以用下面的二维码</p>
            <img style="width:5rem;" src="https://dev.azure.com/cjlspy/844861b8-ee45-48a3-b430-b974684baaaa/_apis/git/repositories/bf4da1c1-1cfa-4f54-8a75-745cce2b737c/items?versionDescriptor%5Bversion%5D=master&versionDescriptor%5BversionOptions%5D=0&versionDescriptor%5BversionType%5D=0&resolveLfs=true&%24format=octetStream&api-version=5.0&path=%2F20190424141834.png"/>
            <img style="width:5rem; padding-left: 2rem;" 
            src="https://dev.azure.com/cjlspy/844861b8-ee45-48a3-b430-b974684baaaa/_apis/git/repositories/bf4da1c1-1cfa-4f54-8a75-745cce2b737c/items?versionDescriptor%5Bversion%5D=master&versionDescriptor%5BversionOptions%5D=0&versionDescriptor%5BversionType%5D=0&resolveLfs=true&%24format=octetStream&api-version=5.0&path=%2F20190424141931.png"/>
            <img style="width:5rem; padding-left: 2rem;" 
            src="https://dev.azure.com/cjlspy/844861b8-ee45-48a3-b430-b974684baaaa/_apis/git/repositories/bf4da1c1-1cfa-4f54-8a75-745cce2b737c/items?versionDescriptor%5Bversion%5D=master&versionDescriptor%5BversionOptions%5D=0&versionDescriptor%5BversionType%5D=0&resolveLfs=true&%24format=octetStream&api-version=5.0&path=%2F20190424142013.png"/>
        </div>
        &copy; 2018 <a href="http://www.kikt.top">caijinglong的博客 By caijinglong</a>.
        Powered by <a rel="nofollow noreferer noopener" href="https://gohugo.io" target="_blank">Hugo</a>.
        <a href="http://www.flysnow.org/" target="_blank">Theme</a> based on <a href="https://github.com/rujews/maupassant-hugo" target="_blank">maupassant</a>.<br/>
        本站所有内容基于<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh" style="color:coral">CC4.0</a>协议发布,需要转载必须署名
        <br/>
        如果有问题可以<a href="mailto:cjl_spy@163.com">email联系我</a>
        <br/>
        京ICP备18038473号-1
        
    </div>
</footer>


    <script type="text/javascript" src="/js/prism.js" async="true"></script>
    <script type="text/javascript">
    
    (function(){
        $("pre code").parent().addClass("line-numbers")
    }())

    window.MathJax = {
        tex2jax: {
            inlineMath: [ ['$','$'] ],
            processEscapes: true
        }
    };
    </script>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML' async></script>

<a id="rocket" href="#top"></a>
<script type="text/javascript" src="/js/totop.js?v=0.0.0" async=""></script>



<script type="text/javascript" src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" async></script>




</body>
</html>
